#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @FileName  :chapter06.py
# @Time      :2021/04/01 11:08:40
# @Author    :Raink


from datetime import datetime, timedelta
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi.security import OAuth2PasswordRequestForm
from pydantic import BaseModel, EmailStr
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, status

from fastapi.security import OAuth2PasswordBearer, oauth2
from starlette.status import HTTP_400_BAD_REQUEST

app06 = APIRouter()

"""OAuth2密码模式和FastAPI的OAuth2PasawordBearer"""

"""
OAuth2PasswordBearer是接收URL作为参数的一个类：
客户端会向该URL发送username和password参数，然后得到一个token值
"""

oauth2_schema = OAuth2PasswordBearer(tokenUrl="/chapter06/token")


@app06.get("/oauth2_password_bearer")
async def oauth2_password_bearer(token: str = Depends(oauth2_schema)):
    return token

"""
基于Password和Beaer token的OAuth2认证
"""
# 假设数据库中的数据如下
fake_users_db = {
    "john snow": {
        "username": "john snow",
        "full_name": "John Snow",
        "email": "johnsnow@example.com",
        "hashed_password": "fakehashedsecret",
        "disabled": False  # 模拟权限
    },
    "raink":
    {
        "username": "raink",
        "email": "user@example.com",
        "full_name": "string",
        "disabled": True,
        "hashed_password": "$2b$12$NrXg1TrpdlVTGwVm4CAH7OJ.iHBr06LUXWZ5yXzQXA4kmgHtArwe."
    }
}

# 假设的密码加密方式


def fake_hash_password(password: str):
    return "fakehashed" + password


"""
使用pydantic的
建立一个用户模型类
"""


class User(BaseModel):
    username: str
    email: Optional[EmailStr] = None
    full_name: Optional[str] = None
    disabled: Optional[bool] = None


class UserInDB(User):
    hashed_password: str


def get_user(db, username: str):
    # 获取所有用户
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)
    return None


def fake_decode_token(token: str):
    user = get_user(fake_users_db, token)
    return user


# chapter06已经有地址http://192.168.1.52:4539/chapter06/，这里直接加上/token，和上面的oauth2_schema一致
# 直接使用fastapi.security 中的 OAuth2PasswordRequestForm

@app06.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user_dict = fake_users_db.get(form_data.username)
    if not user_dict:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Incorrect username or password"
        )
    user = UserInDB(**user_dict)
    hashed_password = fake_hash_password(form_data.password)
    if not hashed_password == user.hashed_password:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Incorrect username or password"
        )
    return {"access_token": user.username, "token_type": "bearer"}

# 教程中这里是使用了依赖注入，依赖了oauth2_schema
# 其产生的效果就是在docs页面中，不会以正常的文本框来填写token，
# 而是在右侧出现 锁子 logo，点击后再次填写用户名，密码，再由系统再次生成token值后再进行验证
# 这里我改成直接填写上一步login方法中生成的access_token，来进行验证，效果更能说明问题


async def get_current_user(token: str):  # = Depends(oauth2_schema)):
    # 获取当前用户
    user = fake_decode_token(token)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authenticantion credentials",
            headers={"WWW-Authenticate": "Bearer"}  # OAuth2的规范
        )
    return user


async def get_current_active_user(current_ueser: User = Depends(get_current_user)):
    # 获取当前活跃用户
    if current_ueser.disabled:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Inactivate user"
        )
    return current_ueser


@app06.get("/user/me")
async def read_user_me(current_user: User = Depends(get_current_active_user)):
    return current_user


"""JWT认证"""

fake_users_db.update({
    "john snow": {
        "username": "john snow",
        "full_name": "John Snow",
        "email": "johnsnow@example.com",
        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX30XePaWxn96p36WQoeG6Lruj3vjPGga311W",
        "disabled": False  # 模拟权限
    }
})


# openssl rand -hex 32
SECRET_KEY = "bbeffb68f93e3d17964d5ca5366d03ef4c077d9c82a6b8a3e628ce485874da84"
ALGORITHW = "HS256"  # 算法
ACCESS_TOKEN_EXPIRE_MINUTES = 30  # 访问令牌过期分钟


class Token(BaseModel):
    """返回给用户的token"""
    access_token: str
    token_type: str


# pip install passlib python-jose
# 对用户传过来的密码进行加密

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

oauth2_schema = OAuth2PasswordBearer(tokenUrl="/chapter06/jwt/token")


def verify_password(plain_password: str, hashed_password: str):
    return pwd_context.verify(plain_password, hashed_password)


def jwt_get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)
    return None  # python中可以不用写这一行


def jwt_authenticate_user(db, username: str, password: str):
    user = jwt_get_user(db=db, username=username)
    if not user:
        return False
    if not verify_password(plain_password=password, hashed_password=user.hashed_password):
        return False
    return user


def created_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(
        claims=to_encode, key=SECRET_KEY, algorithm=ALGORITHW)
    return encoded_jwt


@app06.post("/jwt/register")
async def jwt_register(form_data: User, password: str):
    user = form_data.dict()
    hashed_password = pwd_context.encrypt(password)
    user.update({"hashed_password": hashed_password})
    user_in_db = UserInDB(**user)
    fake_users_db.update({form_data.username: user})
    return user_in_db


@app06.post("/jwt/token", response_model=Token)
async def jwt_login(form_data: OAuth2PasswordRequestForm = Depends()):
    user = jwt_authenticate_user(
        db=fake_users_db, username=form_data.username, password=form_data.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"}
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = created_access_token(
        data={"sub": user.username},
        expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}


async def jwt_get_current_user(token: str):  # = Depends(oauth2_schema)):
    # 获取当前用户
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"}
    )
    try:
        payload = jwt.decode(token=token, key=SECRET_KEY,
                             algorithms=[ALGORITHW])
        username = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    user = jwt_get_user(db=fake_users_db, username=username)
    if not user:
        raise credentials_exception
    return user


async def jwt_get_current_active_user(current_ueser: User = Depends(jwt_get_current_user)):
    # 获取当前活跃用户
    if current_ueser.disabled:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Inactivate user"
        )
    return current_ueser


@app06.get("/jwt/user/me")
async def jwt_read_user_me(current_user: User = Depends(jwt_get_current_active_user)):
    return current_user
